<?php
/* 搜索引擎
*
*
*/

namespace hpnWse\nSe {

use \hpnWse\stNumUtil;
use \hpnWse\stStrUtil;
use \hpnWse\stObjUtil;
use \hpnWse\stAryUtil;
use \hpnWse\stDateUtil;

// utf8: u4e00-u9fa5 (中文)

/// 代码库
class stBase
{
	/// 获取默认词典路径
	public static function cGetDftDicPath($a_Name = 'KeyWords.txt')
	{
		return __DIR__ . '/Dic/' . $a_Name;
	}

	/// 字符的代码，即utf16 ← utf8
	/// a_Cha: String，一个utf8字符，占一到三个字节
	/// 返回：Number，若为-1表示未能识别，否则∈[0, 65535]
	public static function cCodeOfCha($a_Cha)
	{
		$l_Len = strlen($a_Cha);
		if (1 == $l_Len)
		{ return ord($a_Cha[0]); }

		if (2 == $l_Len)
		{
			// 110bbbbb 10bbbbbb
			$l_B0 = ord($a_Cha[0]) & 0x0F;
			$l_B1 = ord($a_Cha[1]) & 0x3F;
			return ($l_B0 << 6) + $l_B1;
		}

		if (3 == $l_Len)
		{
			// 1110bbbb 10bbbbbb 10bbbbbb
			$l_B0 = ord($a_Cha[0]) & 0x0F;
			$l_B1 = ord($a_Cha[1]) & 0x3F;
			$l_B2 = ord($a_Cha[2]) & 0x3F;
			return ($l_B0 << 12) + ($l_B1 << 6) + $l_B2;
		}

		return -1;
	}

	/// 代码的字符，即utf8 ← utf16
	/// a_Code：Number，∈[0, 65535]
	/// 返回：String，表示一个字符，空串表示未能识别
	public static function cChaOfCode($a_Code)
	{
		if ($a_Code < 0)
		{ return ''; }

		if ($a_Code < 0x7F)
		{
			return chr($a_Code);
		}

		if ($a_Code < 0x07FF)
		{
			$l_L = $a_Code & 0x3F;
			$l_H = $a_Code >> 6;
			$l_Rst = chr(0xC0 + $l_H);  // 110bbbbb
			$l_Rst .= chr(0x80 + $l_L); // 10bbbbbb
			return $l_Rst;
		}

		if ($a_Code < 0xFFFF)
		{
			$l_L = $a_Code & 0x3F;
			$l_M = ($a_Code >> 6) & 0x3F;
			$l_H = ($a_Code >> 12);
			$l_Rst = chr(0xE0 + $l_H);  // 1110bbbb
			$l_Rst .= chr(0x80 + $l_M); // 10bbbbbb
			$l_Rst .= chr(0x80 + $l_L); // 10bbbbbb
			return $l_Rst;
		}

		return '';
	}

	/// 是ASC？
	/// a_Cc: Number，字符编码
	public static function cIsCha_ASC($a_Cc)
	{
		return $a_Cc <= 127;
	}

	/// 是数字？
	public static function cIsCha_09($a_Cc)
	{
		return (48 <= $a_Cc) && ($a_Cc <= 57);
	}

	/// 是英文字母？
	public static function cIsCha_Az($a_Cc)
	{
		return self::cIsCha_UprAz($a_Cc) || self::cIsCha_LwrAz($a_Cc);
	}

	/// 是英文大写字母？【注意PHP的方法名不区分大小写】
	public static function cIsCha_UprAz($a_Cc)
	{
		return (65 <= $a_Cc) && ($a_Cc <= 90);
	}

	/// 是英文小写字母？【注意PHP的方法名不区分大小写】
	public static function cIsCha_LwrAz($a_Cc)
	{
		return (97 <= $a_Cc) && ($a_Cc <= 122);
	}

	/// 是汉字？
	public static function cIsCha_CC($a_Cc)
	{
		return (0x4e00 <= $a_Cc) && ($a_Cc <= 0x9fa5);
	}

	/// 把文本拆分成段落
	/// a_Rst：二维数组，每个一维数组表示一个段落的字符数组，例如传入"ab\rcd\nef\r\ngh"，返回：
	/// 	[ ['a','b'], ['c','d'], ['e','f'], ['g','h'] ]
	///	【说明：返回这种结构是为了高效地处理utf8字符，因每次调用mb_substr都会从头开始解码】
	public static function cSplTextToParas(&$a_Rst, $a_Text)
	{
		$l_Paras = stStrUtil::cSplToLines($a_Text);
		$l_ParasLen = count($l_Paras);
		for ($l=0; $l<$l_ParasLen; ++$l)
		{
			$a_Rst[] = stStrUtil::cChasFromStr(trim($l_Paras[$l]));
		}
	}

	/// 存取句子词典，用于cSplParaToStns
	public static function cAcsStnDic()
	{
		static $s_Dic;
		if (!$s_Dic)
		{
			$s_Dic = new tDic();
			$s_Dic
				->cAdd('。')->cAdd('？')->cAdd('！')
				->cAdd('。。。')->cAdd('。。。。')->cAdd('。。。。。')->cAdd('。。。。。。')
			//	->cAdd('...')->cAdd('....')->cAdd('.....')->cAdd('......') //【编程语言里可能会有】
				->cAdd('…')->cAdd('……')
			;
		}
		return $s_Dic;
	}

	/// 把段落拆分成句子
	/// a_Rst：二维数组，每个一维数组表示一个句子的字符数组
	/// a_Chas, a_ChasLen: String[], Number，空白将被收缩
	/// a_fQryDic：Number f($a_Chas, $a_ChasLen, $a_Bgn)，查字典函数，详见tDic::cQry()
	/// a_Iclu: Boolean，在句末是否包含标点？
	///【警告：英文标点可能带有非凡语义，例如：
	///	12,345,678
	/// 12.34
	/// 3! = 3 * 2 * 1
	/// 5!! = 5 * 3 * 1
	/// 如果还要支持编程语句，情况就更复杂了，所以建议不要包含英文标点！
	///】
	public static function cSplParaToStns(&$a_Rst, &$a_Chas, $a_ChasLen, $a_fQryDic = null, $a_Iclu = false)
	{
		// 空？
		if ($a_ChasLen <= 0)
		{ return; }

		// 根据标点分隔句子
		$l_Dic = self::cAcsStnDic();
		$l_Stn = array();
		$l_CI = 0;
		while ($l_CI < $a_ChasLen)
		{
			// 字符编码
			$l_C = $a_Chas[$l_CI];
		//	$l_Cc = self::cCodeOfCha($l_C);

			// 标点？
			$l_QDE = $a_fQryDic 
				? $a_fQryDic($a_Chas, $a_ChasLen, $l_CI) 
				: $l_Dic->cQry($a_Chas, $a_ChasLen, $l_CI);
			if ($l_QDE >= 0)
			{
				if ($a_Iclu)
				{
					stAryUtil::cCcatTo($l_Stn, stAryUtil::cSub($a_Chas, $l_CI, $l_QDE + 1));
				}

				$a_Rst[] = $l_Stn;
				$l_Stn = array();
				$l_CI = $l_QDE;
			}
			else
			{
				$l_Stn[] = $l_C;
			}

			++$l_CI;
		}

		if ($l_Stn)
		{
			$a_Rst[] = $l_Stn;
		}
	}

	/// 把句子拆分成短语，假定句子来自cSplParaToStns
	/// a_Rst：形如 [ [短语种类, 字符串或数组], ... ]，其中各个元素顺次表示拆分出的短语
	/// 	短语种类                    字符串或数组
	///		i_DIC：词典中的术语			字符串
	///		i_09: 数字                  字符串
	///		i_EN: 英文字母？				字符串
	///		i_ASC: 其他ASC字符	    	字符串（长为1）
	///		i_CC: 汉字                  字符数组（为了高效遍历，因mb_substr每次都要从头开始解码）
	///		i_ETC: 其他字符             字符串（长为1）
	/// a_Chas, a_ChasLen: String[], Number，含有的连续空白将被收缩 【注意，若收缩则长度会减小，调用后应重新计算！】
	/// a_fQryDic：Number f($a_Chas, $a_ChasLen, $a_Bgn)，查字典函数，详见tDic::cQry()，仅用于ASCII字符开头的词语
	public static function cSplStnToPhrs(&$a_Rst, &$a_Chas, $a_ChasLen, $a_fQryDic = null)
	{
		// 第一遍，收缩空白
		$l_NextWS = true; // 初始为true，起到“trim right”效果
		for ($l_CI = $a_ChasLen-1; $l_CI >= 0; --$l_CI)
		{
			if (stStrUtil::cIsWhtSpc(self::cCodeOfCha($a_Chas[$l_CI])))
			{
				if ($l_NextWS) // 连续空白
				{
					array_splice($a_Chas, $l_CI, 1);
					--$a_ChasLen;
				}
				else
				{ $l_NextWS = true; }
			}
			else
			{ $l_NextWS = false; }
		}

		// “trim left”
		if ($l_NextWS) // 若[0]是空白，该值一定为true
		{
			array_splice($a_Chas, 0, 1);
			--$a_ChasLen;
		}

		// 空？
		if ($a_ChasLen <= 0)
		{ return; }

		// 第二遍，拆分
		$l_Kind = null; // 种类，分离各种人类语言的词，数字，标点符号
	//	$l_Phrs = array(); // 所有短语，对于要进行分词的先用null占位 【a_Rst】
		$l_Phr = array(); // 累积各类短语的字符数组
		$l_CI = 0; // 字符索引

		$l_fChgKind = function ($a_NewKind) use(&$l_Kind, &$a_Rst, &$l_Phr)
		{
			if ((null !== $l_Kind) && ($l_Kind === $a_NewKind))
			{ return; }

			if ($l_Phr) // 空数组转为false
			{
				// i_09和i_EN连成字符串
				if (('i_09' === $l_Kind) || ('i_EN' === $l_Kind))
				{ $l_Phr = implode('', $l_Phr); }

				$a_Rst[] = array($l_Kind, $l_Phr);
				$l_Phr = array();
			}

			$l_Kind = $a_NewKind;
		};

		while ($l_CI < $a_ChasLen)
		{
			// 字符编码
			$l_C = $a_Chas[$l_CI];
			$l_Cc = self::cCodeOfCha($l_C);

			// 跳过空白
			if (stStrUtil::cIsWhtSpc($l_Cc))
			{
				$l_fChgKind(' ');
				++$l_CI;
				continue;
			}

			// 词典？
			if ($a_fQryDic)// && self::cIsCha_ASC($l_Cc)) //【允许以汉字开头】
			{
				$l_QDE = $a_fQryDic($a_Chas, $a_ChasLen, $l_CI);
				if ($l_QDE >= 0)
				{
					$l_fChgKind('i_DIC'); // 中断，防止后面遇到连字符等产生干扰
					$a_Rst[] = array('i_DIC', implode('', stAryUtil::cSub($a_Chas, $l_CI, $l_QDE + 1)));
					$l_CI = $l_QDE + 1;
					continue;
				}
			}

			// 数字？
			if (self::cIsCha_09($l_Cc))
			{
				$l_fChgKind('i_09');
				$l_Phr[] = $l_C;
				++$l_CI;
				continue;
			}

			// 英文字母？
			if (self::cIsCha_Az($l_Cc))
			{
				$l_fChgKind('i_EN');
				$l_Phr[] = $l_C;
				++$l_CI;
				continue;
			}

			// 其他ASC字符？
			if ($l_Cc <= 127)
			{
				// 如果当前种类是数字，且是“, .”，且后面仍是数字，算作分隔符（不录入）和小数点（录入）
				if (('i_09' === $l_Kind) && ((',' === $l_C) || ('.' === $l_C)) &&
					isset($a_Chas[$l_CI + 1]) && self::cIsCha_09(ord($a_Chas[$l_CI + 1])))
				{
					if ('.' === $l_C)
					{ $l_Phr[] = $l_C; }
				}
				else // 如果当前种类是英文，且是“- '”，且后面仍是英文，算作连字符（不录入）和缩写记号（录入）
				if (('i_EN' === $l_Kind) && (('-' === $l_C) || ("'" === $l_C)) &&
					isset($a_Chas[$l_CI + 1]) && self::cIsCha_Az(ord($a_Chas[$l_CI + 1])))
				{
					//【警告：可能有这种情况“the-city-that-never-sleeps”，此时应算作空格，暂时忽略】
					if ("'" === $l_C)
					{ $l_Phr[] = $l_C; }
				}
				else
				{
					$l_fChgKind('i_ASC');
					$a_Rst[] = array('i_ASC', $l_C);
				}
				
				++$l_CI;
				continue;
			}

			// 汉字？
			if (self::cIsCha_CC($l_Cc))
			{
				$l_fChgKind('i_CC');
				$l_Phr[] = $l_C;
				++$l_CI;
				continue;
			}

			// i_ETC
			$l_fChgKind('i_ETC');
			$a_Rst[] = array('i_ETC', $l_C);
			++$l_CI;
		}
		
		$l_fChgKind(null); // 补上最后一个
	}

	/// 根据词典拆分短语成文字段
	/// a_Rst：Array，若元素是Array则表示待处理的文字段，否则是String表示词典中的词
	/// a_fQryDic：Number f($a_Chas, $a_ChasLen, $a_Bgn)，查字典函数，详见tDic::cQry()
	/// a_Ers: Boolean，当查到时（a_fQryDic()>=0）是否擦除？默认false
	public static function cSplPhrToSpansByDic(&$a_Rst, &$a_Chas, $a_ChasLen, $a_fQryDic, $a_Ers = false)
	{
		$l_Span = $l_Bgn = 0;
		while ($l_Bgn < $a_ChasLen)
		{
			$l_End = $a_fQryDic($a_Chas, $a_ChasLen, $l_Bgn);
			if ($l_End < 0)
			{
				++$l_Bgn;
				continue;
			}

			if ($l_Bgn - $l_Span > 0)
			{ $a_Rst[] = stAryUtil::cSub($a_Chas, $l_Span, $l_Bgn); }
			if (!$a_Ers)
			{ $a_Rst[] = implode('', stAryUtil::cSub($a_Chas, $l_Bgn, $l_End + 1)); }
			$l_Span = $l_Bgn = $l_End + 1;
		}
		if ($l_Bgn - $l_Span > 0)
		{ $a_Rst[] = stAryUtil::cSub($a_Chas, $l_Span, $l_Bgn); }
	}

	/// 计算长度
	/// a_Chas: String$String[]，字符串或字符数组
	public static function cCalcLen(&$a_Chas)
	{
		return is_string($a_Chas) ? strlen($a_Chas) : count($a_Chas);
	}
}

/// 词典，一般配合中文分词器和索引器使用
class tDic
{
	public $c_Name = ''; /// 名字
	public $c_Trie; /// 检索树，形如 { a: [0, { b: [1, { c: [1, {}] }] }] }
	public $c_Off = 0; // 停用？
	public $c_CaseSens = false; // 大小写敏感？

	/// 构造
	public function __construct($a_Name = '')
	{
		$this->c_Name = $a_Name;
		$this->c_Trie = array();
	}

	/// 拷贝，c_Off一定为0，其余属性相同
	public static function scCopy($a_Orig)
	{
		$l_Copy = new tDic($a_Orig->c_Name);
		$l_Copy->c_Trie = $a_Orig->c_Trie; // 数组赋值本身就是深拷贝
		$l_Copy->c_CaseSens = $a_Orig->c_CaseSens;
		return $l_Copy;
	}

	/// 从文件添加，假定每个词独占一行，
	/// a_Cmt: String，注释字符
	public function cAddFromFile($a_Path, $a_Cmt = '#')
	{
		$l_Data = file_get_contents($a_Path);
		if (!$l_Data)
		{ 
			// 不抛异常了，可能是空文件
		//	throw new \Exception('tDic.cAddFromFile(): 加载文件失败'); 
			return $this;
		}

		// 为节省内存，不调用stStrUtil::cSplToLines()
		$l_Len = strlen($l_Data);
		$b = 0;
		for ($i=0; $i<$l_Len; ++$i)
		{
			if ("\r" == $l_Data[$i])
			{
				if (($i - $b > 0) && ($l_Data[$b] !== $a_Cmt))
				{ $this->cAdd(substr($l_Data, $b, $i - $b)); }

				if ("\n" == $l_Data[$i+1]) // \r\n
				{ ++$i; }

				$b = $i + 1;
			}
			else
			if ("\n" == $l_Data[$i])
			{
				if (($i - $b > 0) && ($l_Data[$b] !== $a_Cmt))
				{ $this->cAdd(substr($l_Data, $b, $i - $b)); }

				$b = $i + 1;
			}
		}

		if (($b < $l_Len) && ($l_Data[$b] !== $a_Cmt))
		{
			$this->cAdd(substr($l_Data, $b));
		}
		return $this;
	}

	/// 添加词
	/// a_Term: String$Char[]，词的字符串或字符数组
	public function cAdd($a_Term)
	{
		$l_Chas = is_array($a_Term) ? $a_Term : stStrUtil::cChasFromStr($a_Term);
		$l_Len = count($l_Chas);
		$l_Tgt = &$this->c_Trie;
		for ($i=0; $i<$l_Len; ++$i)
		{
			$l_C = $l_Chas[$i];
			if (!isset($l_Tgt[$l_C]))
			{ $l_Tgt[$l_C] = array(0, array()); }

			if ($i < $l_Len-1)
			{ $l_Tgt = &$l_Tgt[$l_C][1]; }
			else
			{ $l_Tgt[$l_C][0] = 1; }
		}
		return $this;
	}

	/// 查询
	/// a_Chas: String$Char[]，字符串或字符数组
	/// a_ChasLen: count(a_Chas)，若为0则计算
	/// a_Bgn：Number，起始索引
	/// 返回：a_Chas索引，找到时≥0，否则-1
	public function cQry(&$a_Chas, $a_ChasLen = 0, $a_Bgn = 0)
	{
		if (is_string($a_Chas)) { $a_Chas = stStrUtil::cChasFromStr($a_Chas); $a_ChasLen = 0; }
		if (!$a_ChasLen) { $a_ChasLen = count($a_Chas); }

		$l_Tgt = &$this->c_Trie;
		$l_Prn = null;
		$l_C = null;
		$l_Idx = $a_Bgn;
		for (; $l_Idx < $a_ChasLen; ++$l_Idx)
		{
			$l_C = $a_Chas[$l_Idx];
			if (!isset($l_Tgt[$l_C]))
			{
				if ($this->c_CaseSens)
				{ break; }

				$l_Cc = ord($l_C); // 只考虑首字节即可
				if (stBase::cIsCha_LwrAz($l_Cc))
				{ $l_C = strtoupper($l_C); }
				else
				if (stBase::cIsCha_UprAz($l_Cc))
				{ $l_C = strtolower($l_C); }
				else
				{ break; }

				if (!isset($l_Tgt[$l_C]))
				{ break; }
			}

			$l_Prn = &$l_Tgt;
			$l_LC = $l_C;
			$l_Tgt = &$l_Tgt[$l_C][1];
		}
		return ($l_Prn && (null !== $l_LC) && (1 === $l_Prn[$l_LC][0])) ? ($l_Idx - 1) : -1;
	}

	/// 有？
	/// 返回：Boolean
	public function cHas($a_Str)
	{
		$l_Chas = stStrUtil::cChasFromStr($a_Str);
		$l_ChasLen = count($l_Chas);
		$l_End = $this->cQry($l_Chas, $l_ChasLen, 0);
		return ($l_End === $l_ChasLen - 1);
	}
}

/// 中文分词（Chinese Word Segmentation）
class atCws
{
	public $c_Dics = array(); // tDic[]，词典数组，将依序查找

	/// 构造
	public function __construct()
	{ }

	/// 根据名称存取词典
	public function cAcsDic($a_Name)
	{
		$l_Dics = &$this->c_Dics;
		$l_Len = count($l_Dics);
		for ($i=0; $i<$l_Len; ++$i)
		{
			if ($l_Dics[$i] && ($l_Dics[$i]->c_Name == $a_Name))
			{ return $l_Dics[$i]; }
		}
		return $l_Dics[$i];
	}

	/// 从文件追加词典
	public function cApdDicFromFile($a_Name, $a_Path)
	{
		$l_Dic = new tDic($a_Name);
		$l_Dic->cAddFromFile($a_Path);
		$this->c_Dics[] = $l_Dic;
		return $this;
	}

	/// 开关词典
	public function cSwchDic($a_Name, $a_OnOff)
	{
		$l_Dic = $this->cAcsDic($a_Name);
		if ($l_Dic) { $l_Dic->c_Off = $a_OnOff ? 0 : 1; }
		return $this;
	}

	/// 运行
	/// a_Rst：a_WithKind为false时String[]，每个元素是一个词语；
	///		为true时[种类, 词语][]
	/// a_Text: String，utf8编码的文本
	/// a_PsrvPhr: Boolean，保留短语（不分词）？默认false
	/// a_WithKind: Boolean，带着词语种类？见stBase::cSplStnToPhrs()的短语种类
	public function cRun(&$a_Rst, $a_Text, $a_PsrvPhr = false, $a_WithKind = false)
	{
		// 特殊处理
		if ('0' === $a_Text) { return array('0'); }
		if (!$a_Text) { return array(); }

		// 虚函数
		return $this->vdRun($a_Rst, $a_Text, $a_PsrvPhr, $a_WithKind);
	}

	/// 运行
	public function vdRun(&$a_Rst, $a_Text, $a_PsrvPhr, $a_WithKind)
	{
		throw new \Exception('纯虚函数！');
	}

	/// 有词典？
	public function cHasDic()
	{
		return $this->c_Dics; // 空数组为false
	}

	/// 查词典
	public function cQryDic(&$a_Chas, $a_ChasLen, $a_Bgn)
	{
		$l_Dics = &$this->c_Dics;
		$l_Len = count($l_Dics);
		for ($i=0; $i<$l_Len; ++$i)
		{
			$l_Dic = $l_Dics[$i];
			$l_Idx = ($l_Dic && !$l_Dic->c_Off) ? $l_Dic->cQry($a_Chas, $a_ChasLen, $a_Bgn) : -1;
			if ($l_Idx >= 0)
			{ return $l_Idx; }
		}
		return -1;
	}

	/// 根据词典拆分句子成短语
	/// 返回：Array，若元素是Array则表示待分词的短语，否则是String表示词典中的词
	public function dSplStnToPhrsByDic(&$a_Rst, &$a_Chas, $a_ChasLen)
	{
		$l_This = $this;
		return stBase::cSplStnToPhrs($a_Rst, $a_Chas, $a_ChasLen, 
			function ($a_Chas, $a_ChasLen, $a_Bgn) use($l_This)
			{
				return $l_This->cQryDic($a_Chas, $a_ChasLen, $a_Bgn);
			});
	}

	/// 根据词典拆分短语成文字段
	/// 返回：Array，若元素是Array则表示待分词的短语，否则是String表示词典中的词
	public function dSplPhrToSpansByDic(&$a_Rst, &$a_Chas, $a_ChasLen)
	{
		$l_This = $this;
		return stBase::cSplPhrToSpansByDic($a_Rst, $a_Chas, $a_ChasLen, 
			function ($a_Chas, $a_ChasLen, $a_Bgn) use($l_This)
			{
				return $l_This->cQryDic($a_Chas, $a_ChasLen, $a_Bgn);
			});
	}

	/// 向结果追加字符串
	/// a_Rst：String[]，
	/// a_Strs: String$String[]，字符串或字符串数组
	/// $a_Kind: String，种类，若非null和'0'则以[a_Kind, a_Str]的形式追加
	public function dApdToRst(&$a_Rst, &$a_Strs, $a_Kind = null)
	{
		$l_Str = is_array($a_Strs) ? implode('', $a_Strs) : $a_Strs;
		$a_Rst[] = $a_Kind ? array($a_Kind, $l_Str) : $l_Str;
	}
}

/// 索引器
class atIdxr
{
	/// 两个词典可以配合使用，例如，当想排除“的”但保留“的确”时，可在词典中这么写：
	/// 的
	/// !的确
	/// “的”将进入c_StopDic，“的确”将进入c_AntiStopDic
	public $c_StopDic = null; /// 停止词典
	public $c_AntiStopDic = null; /// 反停止词典

	/// 构造
	public function __construct()
	{ }

	/// 添加默认停止词典
	public function cAddDftStopDic()
	{
		return $this->cAddStopDicFromFile(__DIR__ . '/Dic/StopWords.txt');
	}

	/// 从文件添加停止词典
	public function cAddStopDicFromFile($a_Path)
	{
		if (!$this->c_StopDic)
		{
			$this->c_StopDic = new tDic();
			$this->c_AntiStopDic = new tDic();
		}

		$this->c_StopDic->cAddFromFile($a_Path);

		// 转移“!”
		if (isset($this->c_StopDic->c_Trie['!']))
		{
			$this->c_AntiStopDic->c_Trie = $this->c_StopDic->c_Trie['!'][1];
			unset($this->c_StopDic->c_Trie['!']);
		}
		return $this;
	}

	/// 查询停止词，仅当在c_StopDic里但不在c_AntiStopDic里，注意后者的词至少含2个字符
	/// 返回：Number，见tDic::cQry()
	public function cQryStopWord(&$a_Chas, $a_ChasLen, $a_Bgn)
	{
		if (!$this->c_StopDic || $this->c_StopDic->c_Off) { return -1; }
		$l_Idx = $this->c_StopDic->cQry($a_Chas, $a_ChasLen, $a_Bgn);
		if ($l_Idx < 0) { return -1; }
		if ($this->c_AntiStopDic->c_Off) { return $l_Idx; }
		return ((1 == $a_ChasLen) || ($this->c_AntiStopDic->cQry($a_Chas, $a_ChasLen, $a_Bgn) < 0)) ? $l_Idx : -1;
	}
}

} // namespace hpnWse\nSe

//////////////////////////////////// OVER ////////////////////////////////////